#include <iostream>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <ctime>
#include <climits>
//#include "AETG.h"
#include "PairingFunction.h"
#include "Order.h"
//#include "Test.h"
#include "Candidate.h"
#include "TestSuite.h"

using namespace std;

#define NUM_TESTSUITE_PER_RESULT 100
#define PRINT_TABLE_OFF
#define PRINT_PAIRS_OFF
#define PRINT_TOTAL_NUM_PAIRS_OFF
#define PRINT_TEST_GENERATED_OFF
#define PRINT_ORDER_OFF
#define PRINT_CHOSEN_TEST_OFF
#define HASH_COLLISION_TESTING_OFF

int main () {
  int numFactors=4;
  int numLevels=3;

  cout << "Input number of factors: "; cin >> numFactors;
  cout << "Input number of levels: ";  cin >> numLevels;
  cout << endl;

  vector< vector<int> > valueTable(numFactors, vector<int>(numLevels,0));
  int cFactor, cLevel, value=0;
  for (cFactor = 0; cFactor < numFactors; cFactor++) {
    for (cLevel = 0; cLevel < numLevels; cLevel++) {
      valueTable[cFactor][cLevel] = value++;
#ifdef PRINT_TABLE_ON
      cout << "(" << cFactor << "," << cLevel << "): " << valueTable[cFactor][cLevel] << endl;
#endif
    }
#ifdef PRINT_TABLE_ON
    cout << endl;
#endif
  }

  // GENERATE ALL PAIRS
  int currFactor, currLevel;
  int pFactor, pLevel;
  int numOfPairs = 0;
  int *hashArray = (int *) calloc (100000, sizeof(int)); // 1 = this pairs has been hashed

  // Traverse all the values in the table
  for (currFactor = 0; currFactor < numFactors; currFactor++) {
    for (currLevel = 0; currLevel < numLevels; currLevel++) {
      // pair the current value (at position [currFactor][currValue]) with the values to the RIGHT of the table
      // Evaluation will start at the NEXT factor after currFactor and at the 0th level
      for (pFactor = currFactor + 1; pFactor < numFactors; pFactor++) {
	for (pLevel = 0; pLevel < numLevels; pLevel++) {
#ifdef PRINT_PAIRS_ON
	  cout << "(" << valueTable[currFactor][currLevel] << "," << valueTable[pFactor][pLevel] << ")" << endl;
#endif

#ifdef HASH_COLLISION_TESTING_ON
	  uint64_t hashValue = encode ((uint32_t) valueTable[currFactor][currLevel], (uint32_t)valueTable[pFactor][pLevel]);
	  cout << hashValue << ", " << valueTable[currFactor][currLevel] << ", " << valueTable[pFactor][pLevel] << endl;
	  if (hashArray[hashValue] == 0) hashArray[hashValue] = 1;
	  else cout << "collision" << endl;
#endif
	  numOfPairs++;
	}
      }
    }
  }

#ifdef PRINT_TOTAL_NUM_PAIRS_ON
  cout << "The total number of pairs is " << numOfPairs << endl;
#endif

  Order *order = new Order (numFactors);
  Candidate *candidate = new Candidate ();
  TestSuite *testSuite = new TestSuite();
  TestSuite *bestTestSuite = new TestSuite();
  int numTestsInBestTestSuite = INT_MAX;
  Test *test = new Test (numFactors);
  int *tempArray = (int *) calloc (numLevels, sizeof(int)); // used to store the possible values in a given factor that could be set to that factor
  int tempArray_counter = 0;
  int pairsUncovered = 0;
  int *hashArrayCopy = (int *) calloc (100000, sizeof(int)); // 1 = this pairs has been hashed
  memcpy (hashArrayCopy, hashArray, 100000 * sizeof(int));

  for (int testSuiteCount = 0; testSuiteCount < NUM_TESTSUITE_PER_RESULT; testSuiteCount++) {
    cout << testSuiteCount << "/" << NUM_TESTSUITE_PER_RESULT << endl;
    while (pairsUncovered < numOfPairs) {
      for (int testCount=0; testCount < NUM_TEST_PER_CANDIDATE; testCount ++) {
	order->rescramble();
#ifdef PRINT_ORDER_ON
	order->print();
#endif
	for (int i=0; i<numFactors; i++) {
	  int highest_newPairs = -1; 
	  int factorToFill = order->orderArray[i]; // factor to consider is order->orderArray[i]
	  
	  // value to consider is valueTable [factorToFill][0 - numLevels]
	  for (int j=0; j<numLevels; j++) {
	    int newPairs = test->test_Factor_Value (factorToFill, valueTable[factorToFill][j], hashArrayCopy);
	    //	cout << "Factor: " << factorToFill << " Value: "<< valueTable[factorToFill][j] << " New Pairs: " << newPairs<< endl;
	    if (newPairs == highest_newPairs) { // This value has an equal amount of new pairs as another value
	      // add the "j" level value to the list
	      tempArray[tempArray_counter++] = j;
	    }
	    if (newPairs > highest_newPairs) { // This value has a higher amount of new pairs then the current highest
	      // delete everything in the current newPairs list and replace it with this one
	      highest_newPairs = newPairs;
	      tempArray_counter = 0;
	      tempArray[tempArray_counter++] = j;
	    }
	  }
	  // pick the best value for the factor (factorToFill) and use test->set_Factor_Value to save it
	  // pick a random number between 0 and tempArray_counter
	  //      cout << "printing tempArray(" << tempArray_counter << "): ";
	  
	  int randNum = rand();
	  int tempArray_index = randNum % tempArray_counter;
	  int tempArray_level = tempArray[tempArray_index];
	  int value = valueTable [factorToFill][tempArray_level];
	  //      cout << "Choose Value: " << value << endl;
	  
	  test->set_Factor_Value (factorToFill, value, hashArrayCopy);
	  
	  //      test->set_Factor_Value (factorToFill, valueTable[factorToFill][tempArray[rand() % tempArray_counter]],hashArrayCopy);    
	  // reset the tempArray and it's counter
	  tempArray_counter = 0;
	  memset (tempArray, 0, numLevels *sizeof(int));
	}
	
#ifdef PRINT_TEST_GENERATED_ON
	test->print();
	//    cout << "There are " << test->newPairs << " pairs" << endl;
#endif      
	candidate->inputTest(*test);
	// reset the test structure
	test->clear();
	//    test->print();
	// reset the hashArrayCopy
	memcpy (hashArrayCopy, hashArray, 100000 * sizeof(int));
      }
      
      Test chosenTest =  candidate->chooseTest(hashArray);
#ifdef PRINT_CHOSEN_TEST_ON
      chosenTest.print();
#endif
      if (chosenTest.newPairs > 0) {
	// if there are new pairs, then add this test to the test suite
	testSuite->addTest(chosenTest);
	pairsUncovered += chosenTest.newPairs;
	//	cout << pairsUncovered <<"/" << numOfPairs<< "\r";
      }
      candidate->clear();
    } 

    // reset hash table for new testsuite
    memset (hashArray, 0, 100000 * sizeof(int));
    memcpy (hashArrayCopy, hashArray, 100000 * sizeof(int));

    if ( testSuite->getSize() < numTestsInBestTestSuite) {
      numTestsInBestTestSuite = testSuite->getSize();
      *bestTestSuite = *testSuite;
    }

    //    testSuite->print();
    testSuite->clear();
    tempArray_counter = 0;
    pairsUncovered = 0;
  }

  cout << "RESULT TEST SUITE: " << endl;
  bestTestSuite->print();

  return 0;
}
